package com.jiangli.utils;

import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.List;

@Component
public class SlidingWindowRateLimiter {

    private final StringRedisTemplate stringRedisTemplate;
    private static final long TIME_SLICE = 10000;
    // 构造器注入，兼容JDK 17的依赖注入规范
    public SlidingWindowRateLimiter(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    /**
     * 检查请求是否允许通过（修正参数类型和顺序）
     * @param clientKey 客户端标识（IP或用户ID）
     * @param apiPath 接口路径
     * @param windowSize 窗口大小（毫秒，long类型）
     * @param maxRequests 最大请求数（int类型）
     * @return 是否允许请求
     */
    public boolean allowRequest(String clientKey, String apiPath, long windowSize, int maxRequests) {
        // 生成Redis键（确保格式正确）
        String redisKey = "rate_zset_limit:" + apiPath + ":" + clientKey;
        long currentTime = System.currentTimeMillis();
        long windowStartTime = currentTime - windowSize;

        // Lua脚本：使用ZSET存储时间戳，高效计算窗口内请求数
        String luaScript = """
            -- 删除窗口外的记录
            redis.call('ZREMRANGEBYSCORE', KEYS[1], 0, ARGV[1])
            -- 获取当前窗口内的请求数
            local currentCount = redis.call('ZCARD', KEYS[1])
            -- 判断是否允许请求
            if currentCount < tonumber(ARGV[2]) then
                -- 允许请求，添加当前时间戳到ZSET
                redis.call('ZADD', KEYS[1], ARGV[3], ARGV[3])
                -- 设置过期时间（窗口大小+1秒，确保数据能被清理）
                redis.call('EXPIRE', KEYS[1], ARGV[4])
                return 1
            end
            -- 拒绝请求
            return 0
        """;

        // 执行Lua脚本（参数严格匹配）
        DefaultRedisScript<Long> script = new DefaultRedisScript<>(luaScript, Long.class);
        Long result = stringRedisTemplate.execute(
                script,
                Collections.singletonList(redisKey),
                String.valueOf(windowStartTime),  // ARGV[1]: 窗口起始时间（字符串类型）
                String.valueOf(maxRequests),      // ARGV[2]: 最大请求数（字符串类型）
                String.valueOf(currentTime),      // ARGV[3]: 当前时间戳（字符串类型）
                String.valueOf((windowSize / 1000) + 1)  // ARGV[4]: 过期时间（秒，加1秒避免提前过期）
        );

        // 处理返回结果（防止null）
        return result != null && result == 1;
    }
    public boolean allowRequestByHash(String clientKey, String apiPath, long windowSize, int maxRequests) {
        // 1. 生成Hash的key（格式：rate_limit:接口:客户端）
        String hashKey = "rate_hash_limit:" + apiPath + ":" + clientKey;
        long currentTime = System.currentTimeMillis();

        // 2. 计算当前时间片（field）和窗口内最早时间片
        long currentSlice = currentTime / TIME_SLICE; // 当前时间片标识
        long windowSlices = windowSize / TIME_SLICE;  // 窗口包含的时间片数量
        long minSlice = currentSlice - windowSlices;  // 窗口内最早时间片（小于此值的需删除）

        // 3. Lua脚本：原子性执行“清理过期时间片+累加计数+判断是否超限”
        String luaScript = """
            -- 1. 删除窗口外的时间片（field < minSlice）
            local fields = redis.call('HKEYS', KEYS[1])
            for _, field in ipairs(fields) do
                if tonumber(field) < tonumber(ARGV[1]) then
                    redis.call('HDEL', KEYS[1], field)
                end
            end
            
            -- 2. 累加当前时间片的请求数
            redis.call('HINCRBY', KEYS[1], ARGV[2], 1)
            
            -- 3. 计算窗口内的总请求数
            local total = 0
            local values = redis.call('HVALS', KEYS[1])
            for _, val in ipairs(values) do
                total = total + tonumber(val)
            end
            
            -- 4. 设置Hash过期时间（窗口大小+1个时间片，确保数据能被清理）
            redis.call('EXPIRE', KEYS[1], ARGV[3])
            
            -- 5. 判断是否允许请求
            return total <= tonumber(ARGV[4]) and 1 or 0
        """;

        // 4. 执行Lua脚本
        DefaultRedisScript<Long> script = new DefaultRedisScript<>(luaScript, Long.class);
        Long result = stringRedisTemplate.execute(
                script,
                Collections.singletonList(hashKey),
                String.valueOf(minSlice),          // ARGV[1]: 窗口内最早时间片
                String.valueOf(currentSlice),      // ARGV[2]: 当前时间片
                String.valueOf((windowSize + TIME_SLICE) / 1000),  // ARGV[3]: 过期时间（秒）
                String.valueOf(maxRequests)        // ARGV[4]: 最大请求数
        );

        return result != null && result == 1;
    }
}